# -*- coding: utf-8 -*-

# Copyright (C) 2010 David Byers <david.byers@liu.se>
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyKDE4.plasma import Plasma
from PyKDE4 import plasmascript
from PyKDE4.kdeui import *
from PyKDE4.kdecore import *

from popup import InfoDialog
from configuration import MPDConfig

import mpd

class SeekAmountController(QObject):
    '''Object that maintains incremental seek amounts.'''

    def __init__(self):
        self.enabled = False
        self.seek_times = [(3,1), (3,2), (3,3), (3,4), (8,5), (10,10), (10,20), (-1, 30)]

        self.timer = QTimer()
        self.connect(self.timer, SIGNAL("timeout()"), self.increase)
        self.reset()

    def increase(self):
        "Increase the seem amount. Normally triggered by an internal timeout"
        self.index += 1
        if self.index >= len(self.seek_times):
            self.index = len(self.seek_times)-1
        (interval, self.seek_amount) = self.seek_times[self.index]
        if interval > 0:
            self.timer.setInterval(interval * 1000)
        else:
            self.stop()
    
    def reset(self):
        "Reset the seek amount to the first increment"
        self.index = -1
        self.increase()

    def start(self):
        "Reset the seek amount and start the internal timer"
        self.enabled = True
        self.reset()
        self.timer.start()

    def stop(self):
        "Stop the internal timer and disable increasing"
        self.timer.stop()
        self.enabled = False

    def pause(self):
        "Stop the internal timer temporarily"
        if self.timer.isActive():
            self.timer.stop()

    def resume(self):
        '''Restart the internal timer after it has been paused. 
        If the controller is disabled, then this will do nothing'''
        if self.enabled and not self.timer.isActive():
            self.timer.start()

    def amount(self):
        "Return the current seek amount"
        return self.seek_amount
            


class SeekController(QObject):
    "This object is used to seek in in the MPD server"
    def __init__(self, button=None, client=None, direction=1, threshold=1000, interval=150, click=None):
        '''Create a new SeekController. 

        Parameter button must a Qt object implementing the isUnderMouse method and have signals pressed and clicked
        Parameter client must implement the seek method
        Parameter direction is the seek direction (1 or -1)
        Parameter threshold is the number of milliseconds before seeking starts
        Parameter interval is how often seeking takes place
        Parameter click is a function to call if the user clicks before the threshold is reached
        '''

        self.button = button
        self.threshold = threshold
        self.interval = interval
        self.client = client
        self.direction = direction
        self.click_cb = click

        self.is_repeating = False
        self.amount_controller = SeekAmountController()

        self.timer = QTimer()
        self.connect(self.timer, SIGNAL("timeout()"), self.timeout)

        if self.button:
            self.connect(self.button, SIGNAL("pressed(bool)"), self.press)
            self.connect(self.button, SIGNAL("clicked()"), self.click)

    def timeout(self):
        "Called when the internal timer runs out"
        if not self.is_repeating:
            self.timer.setInterval(self.interval)
            self.amount_controller.start()
            self.is_repeating = True

        if self.button.isUnderMouse():
            self.amount_controller.resume()
            self.seek(self.amount_controller.amount() * self.direction)
        else:
            self.amount_controller.pause()

    def press(self, is_pressed):
        "Called when the user presses the button"
        if is_pressed:
            self.is_repeating = False
            self.timer.setInterval(self.threshold)
            self.timer.start()
        else:
            self.timer.stop()
            self.amount_controller.stop()

    def click(self):
        "Called when the user clicks the button"
        if self.is_repeating:
            pass
        elif self.click_cb:
            self.click_cb()

    def seek(self, amount):
        "Called to seek in the MPD server"
        status = self.client.status()
        if status.get('state','stopped') == 'play':
            song = int(status.get('song',0))
            time = int(status.get('time','0:0').split(':')[0]) + amount
            if (time < 0):
                song = song - 1
                if (song >= 0):
                    info = self.client.playlistinfo(song)[0]
                    time = time + int(info.get('time','0'))
                else:
                    return
            self.client.seek(song, time)


class MPDController(plasmascript.Applet):
    def __init__(self, parent, args=None):
        plasmascript.Applet.__init__(self, parent)
        self.client = mpd.MPDClient()
        self.error_message = ""
        self.error_extra = ""

    def init(self):
        self.setHasConfigurationInterface(True)
        self.setAspectRatioMode(Plasma.IgnoreAspectRatio)
        self.theme = Plasma.Svg(self)
        self.setBackgroundHints(Plasma.Applet.DefaultBackground)

        self.layout = QGraphicsLinearLayout(Qt.Horizontal, self.applet)
        self.layout.setContentsMargins(0,2,0,0)
        self.layout.setSpacing(1)

        self.settings = {}
        gc = self.config()

        self.settings["server"] = self.readEntry(gc, "server", "");
        self.settings["port"] = int(self.readEntry(gc, "port", "6600"));

        self.resize(300,24)
        self.applet.setLayout(self.layout)

        self.pause_icon = KIcon("media-playback-pause")
        self.start_icon = KIcon("media-playback-start")
        self.offline_icon = KIcon("dialog-warning")
        self.online_icon = KIcon("dialog-information")
        self.error_icon = KIcon("dialog-error")

        self.statusButton = self.create_button(self.offline_icon, self.toggle_status)
        self.playButton = self.create_button(self.start_icon, self.play_pause)
        self.prevButton = self.create_button(KIcon("media-skip-backward"))
        self.nextButton = self.create_button(KIcon("media-skip-forward"))
        self.stopButton = self.create_button(KIcon("media-playback-stop"), self.mpd_stop)

        self.ffw_controller = SeekController(button=self.nextButton, client=self.client, direction=1, click=self.mpd_next)
        self.rew_controller = SeekController(button=self.prevButton, client=self.client, direction=-1, click=self.mpd_prev)
        
        self.volume = Plasma.Slider()
        self.volume.setOrientation(Qt.Horizontal)
        self.volume.setRange(0,100)
        self.connect(self.volume,SIGNAL("valueChanged(int)"),self.set_volume)

        self.layout.addItem(self.statusButton)
        self.layout.addItem(self.prevButton)
        self.layout.addItem(self.stopButton)
        self.layout.addItem(self.playButton)
        self.layout.addItem(self.nextButton)
        self.layout.addItem(self.volume)

        self.timer = QTimer()
        self.timer.setInterval(1000)
        self.connect(self.timer, SIGNAL("timeout()"), self.update)

        self.dialog = InfoDialog()
        self.dialog.init()
        self.dialogTimer = QTimer()
        self.dialogTimer.setSingleShot(True)
        self.connect(self.dialogTimer, SIGNAL("timeout()"), self.show_status)

        self.mpd_connect()

        self.timer.start()
        self.update()

    def set_error(self, message, extra=""):
        self.error_message = message
        self.error_extra = extra
        self.statusButton.setIcon(self.error_icon)

    def clear_error(self):
        self.error_message = ""
        self.error_extra = ""
        self.statusButton.setIcon(self.online_icon)

    def create_button(self, icon, cb=None):
        button = Plasma.IconWidget(icon, '', self.applet)
        button.setMaximumSize(24, 24)
        if cb:
            self.connect(button, SIGNAL("clicked()"), cb)
        return button

    def hoverEnterEvent(self, event):
        self.dialogTimer.start(1000)

    def hoverLeaveEvent(self, event):
        self.dialogTimer.stop()
        self.hide_status()

    def mpd_connect(self):
        try:
            self.client.disconnect()
        except:
            pass

        try:
            if self.settings["server"] and self.settings["port"]:
                self.client.connect(self.settings["server"], self.settings["port"])
                self.clear_error()
            else:
                self.set_error("No server configured", "Open the playback controller settings")
        except Exception,e:
            self.set_error("Unable to connect to " + self.settings["server"] + ":" + str(self.settings["port"]), str(e))

    def update_dialog(self, status=None):
        if self.error_message:
            self.dialog.setData(self.error_message, self.error_extra, "")
            self.dialog.setProgress(1)

        elif status:
            try:
                song = self.client.currentsong()
                if (song):
                    if status.get('state','stopped') == 'play':
                        state = ""
                    elif status.get('time',None):
                        state = " (paused)"
                    else:
                        state = " (stopped)"
                    self.dialog.setData(song.get('title','Unknown track') + state,
                                        artist=song.get('artist','Unknown artist'),
                                        album=song.get('album','Unknown album'))
                    (time,total) = status.get('time','0:100').split(':')
                    self.dialog.setProgress(float(time)/float(total))
                else:
                    self.dialog.setData("Nothing playing", "", "")
            except Exception,e:
                self.dialog.setData("Unable to get status from server", str(e), "")
        else:
            pass
        

    def update(self):
        try:
            status = self.client.status()
            if status.get('state','stopped') == 'play':
                self.playButton.setIcon(self.pause_icon)
            else:
                self.playButton.setIcon(self.start_icon)

            volume = int(status.get('volume',100))
            if self.volume.value() != volume:
                self.volume.setValue(volume)
            self.clear_error()
            self.update_dialog(status)
        except Exception,e:
            if not self.error_message:
                self.set_error(str(e))
            self.update_dialog(None)

    def play_pause(self):
        try:
            status = self.client.status()
            if status.get('state','stopped') == 'play':
                self.client.pause(1)
            elif status.get('state','stopped') == 'pause':
                self.client.play()
            else:
                self.client.play()
            self.update()
        except:
            pass

    def mpd_prev(self):
        try:
            self.client.previous()
            self.update()
        except:
            pass

    def mpd_stop(self):
        try:
            self.client.stop()
            self.update()
        except:
            pass

    def mpd_next(self):
        try:
            self.client.next()
            self.update()
        except:
            pass

    def set_volume(self,value):
        try:
            self.client.setvol(value)
            self.update()
        except:
            pass

    def show_status(self):
        if self.configurationRequired():
            self.showConfigurationInterface()
        else:
            self.dialog.setMinimumWidth(self.size().width())
            self.dialog.move(self.popupPosition(self.dialog.sizeHint()))
            self.dialog.show()

    def hide_status(self):
        if not self.dialog.persistent:
            self.dialog.hide()

    def toggle_status(self):
        if self.dialog.isVisible():
            self.dialog.persistent = False
            self.hide_status()
        else:
            self.dialog.persistent = True
            self.show_status()

    def configurationRequired(self):
        return not self.settings["server"] or not self.settings["port"]

    def createConfigurationInterface(self, parent):
        self.mpd_config = MPDConfig(self, self.settings)
        p = parent.addPage(self.mpd_config, "MPD Server")
        p.setIcon(KIcon("preferences-desktop-multimedia"))
        
        self.connect(parent, SIGNAL("okClicked()"), self.configAccepted)
        self.connect(parent, SIGNAL("cancelClicked()"), self.configDenied)

#    def showConfigurationInterface(self):
#        pass

    def configAccepted(self):
        self.settings = self.mpd_config.exportSettings()
        gc = self.config()

        gc.writeEntry("server", self.settings["server"])
        gc.writeEntry("port", str(self.settings["port"]))
        
        self.mpd_connect()

    def configDenied(self):
        self.mpd_config.deleteLater()

    def readEntry(self, gc, key, default):
        val = gc.readEntry(key, default)
        if type(val) == QVariant:
            return str(val.toString())
        else:
            return val
 
def CreateApplet(parent):
    return MPDController(parent)
